/*
	This reads in all the info for a scene, including stuff we don't actually use.
	The only things we care about are:
	"newModel" - which gives a unique index to a model in the scene (even if there are
	   multiple models with the same id, each gets its own unique index)
	"transform" - the position / orientation
	"children" - the way that SceneViewer works, the rendering is done through a
	   parent-child tree. A parent gets rendered, then its children, then its
	   childrens' children. Not the most efficient way to do things, but it's
	   what Matt originally used for his project - of course, his project revolved 
	   around parent-child relations between objects.
*/
#include "Common.h"

void Scene::Load(const std::string &databaseDirectory, const std::string &sceneShortFilename)
{
	_sceneName = sceneShortFilename;

	std::string sceneFilename = databaseDirectory + "scenes/" + _sceneName;
    std::cout << "Loading Scene: " << sceneFilename << '\n';

    auto lines = Utility::GetFileLines(sceneFilename, 2);
    if (lines.empty())
    {
        std::cout << "Scene file not found: " << sceneFilename << '\n';
		return;
    }
    if(lines[0] != "StanfordSceneDatabase" || lines[1] != "version 1.0")
    {
        std::cout << "This is not a valid scene file.";
		std::cout << "Expecting a Stanford Scene Database v1.0 scene file." << endl;
        std::cout << "Instead, the file is of format: " << lines[0] << endl;
        return;
    }

    ModelInstance *activeModelInstance = NULL;
    for(auto lineIterator = lines.begin(); lineIterator != lines.end(); lineIterator++)
    {
        const std::string &curLine = *lineIterator;
        if(curLine.find("modelCount ") == 0)
        {
            const unsigned int modelCount = Utility::StringToIntegerList(curLine, "modelCount ")[0];
            _allModels.resize(modelCount);
            for(unsigned int modelIndex = 0; modelIndex < modelCount; modelIndex++)
            {
                _allModels[modelIndex] = new ModelInstance;
            }
        }
        if(curLine.find("newModel ") == 0)
        {
            auto parts = Utility::PartitionString(curLine, " ");
            int modelIndex = Utility::StringToInt(parts[1]);
            activeModelInstance = _allModels[modelIndex];

			string modelId = parts[2];
			activeModelInstance->_modelId = modelId;
            activeModelInstance->_model = _assets.GetModel(databaseDirectory, modelId);
        }
        if(curLine.find("transform ") == 0)
        {
            auto transform = Utility::StringToFloatList(curLine, "transform ");
			//if(DEBUG) cout << " Loading transform: " << curLine << endl;
            for(unsigned int elementIndex = 0; elementIndex < 16; elementIndex++)
            {
                activeModelInstance->_transform[0][elementIndex] = transform[elementIndex];
            }
			//if(DEBUG) cout << "New matrix loaded: " << endl;
			//if(DEBUG) activeModelInstance->_transform.printMatrix();
        }
    }

    //_root = _allModels[0];
	cout << "Finished loading scene!" << endl;
}

ModelInstance* Scene::GetModelPointer(int modelIndex) {
	if (modelIndex >= 0 && modelIndex < _allModels.size()) {
		return _allModels[modelIndex];
	}
	else {
		cout << "The modelIndex is out of range" << endl;
		getchar();
		exit(6);
	}
}

vector<ModelInstance*> Scene::GetAllModels() {
	return _allModels;
}

//float Scene::FindBestScalingFactor(Vec3f& oBoxMin, Vec3f& oBoxMax, ModelInstance* newModelInstance) {
//	//Vec3f oBoxMin = originalModelInstance->GetBoundingBoxMin();
//	//Vec3f oBoxMax = originalModelInstance->GetBoundingBoxMax();
//	Vec3f nBoxMin = newModelInstance->GetBoundingBoxMin();
//	Vec3f nBoxMax = newModelInstance->GetBoundingBoxMax();
//	if (DEBUG) {
//		cout << "Original BBOX min: ";
//		oBoxMin.print();
//
//		cout << "New BBOX min: ";
//		nBoxMin.print();
//
//		cout << "Original BBOX max: ";
//		oBoxMax.print();
//
//		cout << "New BBOX max: ";
//		nBoxMax.print();
//
//	}
//
//	float oLengthX = oBoxMax.x - oBoxMin.x;
//	float oLengthY = oBoxMax.y - oBoxMin.y;
//	float oLengthZ = oBoxMax.z - oBoxMin.z;
//
//	float nLengthX = nBoxMax.x - nBoxMin.x;
//	float nLengthY = nBoxMax.y - nBoxMin.y;
//	float nLengthZ = nBoxMax.z - nBoxMin.z;
//
//	float bestScaleFactor = -1;
//	float bestDelta = -1;
//
//	// Try to rescale to match original's x-length first
//	float scaleFactorX = fabs(oLengthX / nLengthX);
//
//	// check whether the y & z lengths are out of bounds
//	if ((nLengthY * scaleFactorX > oLengthY) ||
//		(nLengthZ * scaleFactorX > oLengthZ)) {
//			// discard this scale factor & goto the next
//	}
//	else {
//		float thisDelta = fabs(nLengthY * scaleFactorX - oLengthY) + fabs(nLengthZ * scaleFactorX - oLengthZ);
//		if (bestDelta == -1 || thisDelta < bestDelta) {
//			bestDelta = thisDelta;
//			bestScaleFactor = scaleFactorX;
//		}
//	}
//
//	// Next, try to rescale to match original's y-length
//	float scaleFactorY = fabs(oLengthY / nLengthY);
//
//	// check whether the x & z lengths are out of bounds
//	if ((nLengthX * scaleFactorY > oLengthX) ||
//		(nLengthZ * scaleFactorY > oLengthZ)) {
//			// discard this scale factor & goto the next
//	}
//	else {
//		float thisDelta = fabs(nLengthX * scaleFactorY - oLengthX) + fabs(nLengthZ * scaleFactorY - oLengthZ);
//		if (bestDelta == -1 || thisDelta < bestDelta) {
//			bestDelta = thisDelta;
//			bestScaleFactor = scaleFactorY;
//		}
//	}
//
//	// Finally, try to rescale to match original's Z-length
//	float scaleFactorZ = fabs(oLengthZ / nLengthZ);
//
//	// check whether the x & y lengths are out of bounds
//	if ((nLengthX * scaleFactorZ > oLengthX) ||
//		(nLengthY * scaleFactorZ > oLengthY)) {
//			// discard this scale factor & goto the next
//	}
//	else {
//		float thisDelta = fabs(nLengthX * scaleFactorZ - oLengthX) + fabs(nLengthY * scaleFactorZ - oLengthY);
//		if (bestDelta == -1 || thisDelta < bestDelta) {
//			bestDelta = thisDelta;
//			bestScaleFactor = scaleFactorZ;
//		}
//	}
//
//	if(DEBUG) cout << "Best scale factor: " << bestScaleFactor << ", with delta of " << bestDelta << endl;
//
//	return bestScaleFactor;
//}

//// WARNING: Before calling this function, you should store the original model id in a safe place (or
//// optionally, store the original modelInstance), so that you can restore the original model
//// once you're done
//void Scene::ReplaceModel(int originalModelIndex, string replacementModelId, string databaseDir, 
//	Vec3f oBoxMin, Vec3f oBoxMax) {
////ModelInstance* originalModelInstance) {
//	// We load the correct replacement model
//    ModelInstance *activeModelInstance = _allModels[originalModelIndex];
//    activeModelInstance->_model = _assets.GetModel(databaseDir, replacementModelId);
//
//	Vec3f nBoxMin = activeModelInstance->GetBoundingBoxMin();
//	Vec3f nBoxMax = activeModelInstance->GetBoundingBoxMax();
//
//	// ++TODO Resize the new model so it fits inside the bounding box of the original;
//	// we guess that this is one way to guarantee that the new model will not be clipped,
//	// although this may not work practically for certain things (like a book replaced with
//	// a lamp)
//	float scaleFactor = FindBestScalingFactor(oBoxMin, oBoxMax, activeModelInstance);
//
//	// ++TODO using a constant for now
//	//scaleFactor = 0.75;
//
//
//	// Find the new displacement:
//	Matrix4 oldTransform = activeModelInstance->_transform;
//	cout << "OLD TRANSFORM: " << endl;
//	oldTransform.printMatrix();
//
//	// Find the center of the original box after transforming it (both scaling and translation affect vertices)
//	Vec3f originalCenter = ( oBoxMin + oBoxMax ) / 2;
//	//Vec3f originalBaseCenter = originalCenter;
//	//originalBaseCenter.z = oBoxMin.z;
//
//	Vec3f transformedOriginalCenter = oldTransform.TransformPoint(originalCenter);
//	//Vec3f transformedOriginalBaseCenter = oldTransform.TransformPoint(originalBaseCenter);
//	cout << "Transformed Original center at: ";
//	transformedOriginalCenter.print();
//
//	Matrix4 scalingMatrix = oldTransform.Scaling(Vec3f(scaleFactor, scaleFactor, scaleFactor));
//	cout << "SCALING MATRIX: " << endl;
//	scalingMatrix.printMatrix();
//
//	Matrix4 newTransform = oldTransform * scalingMatrix;
//	cout << "TRANSFORM (BEFORE RE-CENTERING): " << endl;
//	newTransform.printMatrix();
//
//	// Get the center of the replacement model after applying the original transform & scaling
//	Vec3f replacementCenter = (nBoxMin + nBoxMax) / 2;
//	Vec3f transformedReplacementCenter = newTransform.TransformPoint(replacementCenter);
//
//	cout << "Replacement model's center at: ";
//	transformedReplacementCenter.print();
//
//	// We need to shift this center back to where the original model's center was
//	Vec3f necessaryShift = transformedOriginalCenter - transformedReplacementCenter;
//
//	// ++TODO: ignore the z-shift, so that the object stays in contact with whatever was supporting it
//
//	Matrix4 displacementMatrix = newTransform.Translation(necessaryShift);
//	newTransform = newTransform * displacementMatrix;
//
//	cout << "FINAL TRANSFORM: " << endl;
//	newTransform.printMatrix();
//
//	activeModelInstance->_transform = newTransform;
//
//
//}

void Scene::TargetCamera(int modelNumber)
{
	int numModels = (int)this->_allModels.size();

	if (modelNumber == 0) {
		// Use default camera position
		cout << "Using default camera position" << endl;
		if (numModels > 0) {
			cout << "Targeting model 1" << endl;
			modelNumber = 1;
		}
		else {
			cout << "Using arbitrary camera angle" << endl;
		    _camera.Reset(Vec3f(172.35596f, 64.303215f, 142.52835f), Vec3f(0.0f, 0.0f, 1.0f), Vec3f(172.35596f - 0.83745980f, 64.303215f + 0.23592833f, 142.52835f - 0.49295229f));
			return;
		}
	}

	if (modelNumber < 0 || modelNumber >= numModels) {
		cout << "Invalid model index selected for camera targetting!" << endl;
		getchar();
		exit(4);
	}
	else {
		cout << "Targeting model #: " << modelNumber << endl;
	}

	Vec3f roomCenter = this->GetRoomCenterInWorldCoords();
	Vec3f targetModel = this->GetModelCenterInWorldCoords(modelNumber);

	// .Reset(Eye, World Up, LookAt)
    this->_camera.Reset(roomCenter, Vec3f(0.0f, 0.0f, 1.0f), targetModel);
}

void Scene::ResetCamera(const Vec3f &roomCenter, const Vec3f &targetModel)
{
	// .Reset(Eye, World Up, LookAt)
	_camera.Reset(roomCenter, Vec3f(0.0f, 0.0f, 1.0f), targetModel);
}

void Scene::UpdateCamera() {
	_camera.Update();
}

Camera Scene::GetCamera() {
	return _camera;
}

string Scene::GetSceneName() {
	return _sceneName;
}

Vec3f Scene::GetRoomCenterInWorldCoords() {
	ModelInstance* room = _allModels[0];
	return room->GetModelCenterInWorldCoords();
}

Vec3f Scene::GetModelCenterInWorldCoords(int modelNumber) {
	//if (DEBUG) {
	//	printf("# of models: %d\n", _allModels.size());
	//	for (unsigned int i = 0; i < _allModels.size(); i++) {
	//		printf("Model displacement: %f %f %f\n", _allModels.at(i)->_transform[3][0], _allModels.at(i)->_transform[3][1], _allModels.at(i)->_transform[3][2]);
	//	}
	//}

	ModelInstance* model = _allModels.at(modelNumber);
	Vec3f centerWorldCoords = model->GetModelCenterInWorldCoords();
	//if (DEBUG) {
	//	printf("Model Center in World Coords: %f, %f, %f\n", centerWorldCoords.x, centerWorldCoords.y, centerWorldCoords.z);
	//}

	return centerWorldCoords;
}

// This includes model 0 (i.e. the room)
int Scene::GetNumModelsInScene()
{
	return _allModels.size();
}

/*
	glut callback functions
*/
void Scene::ReSize(int windowWidth, int windowHeight)
{
    _windowWidth = windowWidth;
    _windowHeight = windowHeight;
}

void Scene::Update(float seconds)
{
    
}

void Scene::MouseDown(int button, int x, int y)
{
    _mouseButton = button;
    _mouseX = x;
    _mouseY = y;
}

void Scene::MouseMove(int x, int y)
{
    const float angleScale = 0.02f;
    const float moveScale = 1.0f;

    const int mouseDiffX = x - _mouseX;
    const int mouseDiffY = y - _mouseY;

    _mouseX = x;
    _mouseY = y;

    if(_mouseButton == GLUT_LEFT_BUTTON)
    {
        _camera.LookRight(-angleScale * mouseDiffX);
        _camera.LookUp(angleScale * mouseDiffY);
    }
    if(_mouseButton == GLUT_RIGHT_BUTTON)
    {
        _camera.Strafe(-moveScale * mouseDiffX);
        _camera.Jump(-moveScale * mouseDiffY);
    }
}

void Scene::KeyPress(unsigned char key, string screenshotName)
{
    const float moveDistance = 15.0f;
    const float angleDistance = 0.15f;

    switch(key)
    {
    case 'w':
        _camera.Move(-moveDistance);
        break;
    case 's':
        _camera.Move(moveDistance);
        break;
    case 'a':
        _camera.Strafe(moveDistance);
        break;
    case 'd':
        _camera.Strafe(-moveDistance);
        break;
    case 'r':
        _camera.Jump(moveDistance);
        break;
    case 'f':
        _camera.Jump(-moveDistance);
        break;
    case '2':
        _camera.LookUp(angleDistance);
        break;
    case '8':
        _camera.LookUp(-angleDistance);
        break;
    case '4':
        _camera.LookRight(angleDistance);
        break;
    case '6':
        _camera.LookRight(-angleDistance);
        break;

	case 'y':
		printf("Saving screenshot\n");
		SimpleScreenshot(screenshotName);
		break;

    default:
		// we ignore any key-stroke we don't recognize
        break;
    }
}

// Call this only after rendering to the front buffer
void Scene::SimpleScreenshot(const std::string &outputFile) {
	int height = _windowHeight;
	int width = _windowWidth;
	
	STImage* gImage = new STImage(width, height, STColor4ub(0,0,0,0));
	//STImage* gImage = new STImage(width, height);

	// This reads from the front color buffer (GL_FRONT)
	gImage->Read(0,0);

	gImage->Save(outputFile);

}

void Scene::Render()
{
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glViewport(0, 0, _windowWidth, _windowHeight);

    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();

    float aspectRatio = float(_windowWidth) / float(_windowHeight);
    gluPerspective(60.0f, aspectRatio, 0.1f, 1000.0f);
  
    glMatrixMode(GL_MODELVIEW);
    _camera.Update();
    glLoadMatrixf(_camera.Matrix()[0]);

	// Render all
	for (unsigned int i = 0; i < _allModels.size(); i++) {
		_allModels[i]->Render();
	}

	//// Render recursively
	//// parent gets rendered (root parent = room of scene), then all children.
	//_root->Render();

    glutSwapBuffers();
}
